package org.nativejpa.query;

import org.nativejpa.support.sqlgen.SQLGenerator;
import org.nativejpa.mapping.JoinColumnMetadata;
import org.nativejpa.mapping.EntityRepository;
import org.nativejpa.util.XMLUtils;
import org.w3c.dom.Element;

import javax.persistence.NamedNativeQuery;
import javax.persistence.QueryHint;
import java.util.Map;
import java.util.HashMap;
import java.util.List;

/**
 * Store metadata for  @NamedQuery
 * will use velocity to construct native SQL
 * NOTE: use cache.name to specify the cache want to use,  ues query.dialect.xxx to specify a custem database sql query
 \@NamedNativeQuery(
     name = AccountDAOImpl.GET_USERNAME_LIST,
     query = "select username as userid from signon",
     resultClass = String.class,
     hints = {
             \@QueryHint(name = "cache.name", value = "account"),
             \@QueryHint(name = "query.dialect.oracle", value = "select username as userid from signon")
             }

 )

 * @author <a href="mailto:jfox.young@gmail.com">Young Yang</a>
 */
public class NamedSQLTemplate extends SQLTemplate {
    /**
     * Entity class that difines NamedNativeQuery
     */
    private Class<?> definedClass = null;

    /**
     * the cache name be used, if not, will use the define class name as default
     * cache name can be defined explicity in @NamedNativeQuery.hints
     */
    protected String cacheName = "";

    protected String name = "";

    protected Map<String, String> hints = new HashMap<String, String>(4);

    // db => templateSQL
    protected Map<String, String> dbTemplateSQLs = new HashMap<String, String>(3);

    public static final String QUERY_HINT_KEY_CACHE_NAME = "cache.name";
    public static final String QUERY_HINT_KEY_QUERY_DIALECT_PREFIX = "query.dialect.";

    public NamedSQLTemplate(NamedNativeQuery namedNativeQueryAnnotation, Class<?> defineClass) {
        this(namedNativeQueryAnnotation.name(), namedNativeQueryAnnotation.query(), namedNativeQueryAnnotation.resultClass(), namedNativeQueryAnnotation.resultSetMapping(), defineClass, DEFAULT_CACHE_NAME);
        for(QueryHint hint : namedNativeQueryAnnotation.hints()){
            String name = hint.name();
            hints.put(name, hint.value());
            if(name.equals(QUERY_HINT_KEY_CACHE_NAME)) { // cache.name
                cacheName = hint.value();
            }
            else if(name.startsWith(QUERY_HINT_KEY_QUERY_DIALECT_PREFIX)){ // query.dialet
                dbTemplateSQLs.put(name.substring(QUERY_HINT_KEY_QUERY_DIALECT_PREFIX.length()).toUpperCase(), hint.value());
            }
        }
    }

    public NamedSQLTemplate(Element namedNativeQueryElement, Class<?> resultClass, Class<?> defineClass) {
        this(XMLUtils.getAtrributeValue(namedNativeQueryElement, "name"), XMLUtils.getChildElementValueByTagName(namedNativeQueryElement, "query"), resultClass, XMLUtils.getAtrributeValue(namedNativeQueryElement, "result-set-mapping"), defineClass, DEFAULT_CACHE_NAME);
        //TODO: parse hint
        List<Element> hintElements = XMLUtils.getChildElementsByTagName(namedNativeQueryElement, "hint");
    }

    // create a temp NamedSQLTemplate while creating SQLTemplate by SQLGenerator
    public NamedSQLTemplate(String name, String sqlTemplate, Class resultClass, String resultsetMapping, Class<?> defineClass, String cacheName) {
        super(sqlTemplate, resultClass, resultsetMapping);
        if(cacheName == null || cacheName.trim().length() == 0) {
            // use class name as default cache name
            if(defineClass != null) {
                this.cacheName = defineClass.getName();
            }
            else { // no cache specified, use default cache, default cache will be clean if by any update sql operation
                this.cacheName = DEFAULT_CACHE_NAME;
            }
        }
        else {
            this.cacheName = cacheName;
        }
        if(name == null || name.trim().equals("")) {
            throw new IllegalArgumentException("name is null");
        }
        this.name = name;
        this.definedClass = defineClass;
    }

    public Class<?> getDefinedClass() {
        return definedClass;
    }

    public String getName() {
        return name;
    }

    public String getCacheName() {
        return cacheName;
    }

    @Override
    public String getTemplateSQL(String dbProductName) {
        JDBCMapper.DbType dbType = JDBCMapper.detectDatabaseType(dbProductName);
        if(dbTemplateSQLs.containsKey(dbType.toString().toUpperCase())) {
            return dbTemplateSQLs.get(dbType.toString().toUpperCase());
        }
        else {
            return super.getTemplateSQL();
        }
    }

    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer(this.getClass().getSimpleName() + "{" + "name=" + getName() + ", definedClass=" + getDefinedClass().getName() + ", resultClass=" + getResultClass().getName() + ", resultsetMapping=" + getResultsetMapping() + ", templateSQL=" + getTemplateSQL());
        if(!hints.isEmpty()) {
            int i = 0;
            for(Map.Entry<String, String> entry : hints.entrySet()){
                if(i>0) {
                    sb.append(", ");
                }
                sb.append(entry.getKey() + "=" + entry.getValue());

            }
        }
        sb.append("}");
        return sb.toString();
    }
}